(function(exports) {
    var JS_WS_CLIENT_TYPE = 'js-websocket';
    var JS_WS_CLIENT_VERSION = '0.0.1';

    var Protobuf = exports.Protobuf;
    var Protocol = exports.Protocol;
    var Package = Protocol.Package;
    var Message = Protocol.Message;
    var EventEmitter = exports.Emitter;

    var RES_OK = 200;
    var RES_FAIL = 500;
    var RES_OLD_CLIENT = 501;

    if (typeof Object.create !== 'function') {
        Object.create = function(o) {
            function F() {}
            F.prototype = o;
            return new F();
        };
    }

    var pomelo = Object.create(EventEmitter.prototype); // object extend from object
    exports.pomelo = pomelo;

    var curScene = null;

    var socket = null;
    var reqId = 0;
    var callbacks = {};
    var handlers = {};
    //Map from request id to route
    var routeMap = {};

    var heartbeatInterval = 0;
    var heartbeatTimeout = 0;
    var nextHeartbeatTimeout = 0;
    var gapThreshold = 100; // heartbeat gap threashold

    var heartbeatId = null;
    var heartbeatTimeoutId = null;

    var handshakeCallback = null;

    var handshakeBuffer = {
        'sys': {
            type: JS_WS_CLIENT_TYPE,
            version: JS_WS_CLIENT_VERSION
        },
        'user': {}
    };

    var initCallback = null;

    pomelo.init = function(scene,params, cb) {
        curScene = scene;

        initCallback = cb;
        var host = params.host;
        var port = params.port;

        var url = 'ws://' + host;
        if (port) {
            url += ':' + port;
        }

        handshakeBuffer.user = params.user;
        handshakeCallback = params.handshakeCallback;

        initWebSocket(url, cb);

    };

    var initWebSocket = function(url, cb) {
        var onopen = function(event) {
            cc.log("pomelo.onopen");
            var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer)));
            send(obj);
        };

        var onmessage = function(event) {
            cc.log("pomelo.onmessage");
            processPackage(Package.decode(event.data), cb);
            // new package arrived, update the heartbeat timeout
            if (heartbeatTimeout) {
                nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
            }
        };

        var onerror = function(event) {
            cc.log("pomelo.onerror");
            pomelo.emit('io-error', event);
            cc.error('socket error: ', event);
        };

        var onclose = function(event) {
            cc.log("pomelo.onclose");
            pomelo.emit('close', event);
            cc.error('socket close: ', event);
            socket = null;
        };

        socket = new WebSocket(url);
        socket.binaryType = 'arraybuffer';
        socket.onopen = onopen;
        socket.onmessage = onmessage;
        socket.onerror = onerror;
        socket.onclose = onclose;
    };

    // !!!!! 在xcode环境下，不能socket.close()
    pomelo.disconnect = function() {
        cc.log("pomelo.disconnect");
        if (socket) {
//            if (socket.disconnect) socket.disconnect();
//            cc.log("socket.disconnect");
//            if (socket.close) socket.close();
//            cc.log("socket.caodan....");
////            socket.close();
//            cc.log('disconnect');
            socket = null;
        }

        if (heartbeatId) {
            clearTimeout(heartbeatId);
            heartbeatId = null;
        }
        if (heartbeatTimeoutId) {
            clearTimeout(heartbeatTimeoutId);
            heartbeatTimeoutId = null;
        }

    };

    pomelo.request = function(route, msg, cb) {
        if (arguments.length === 2 && typeof msg === 'function') {
            cb = msg;
            msg = {};
        } else {
            msg = msg || {};
        }
        route = route || msg.route;
        if (!route) {
            return;
        }

        reqId++;
        sendMessage(reqId, route, msg);

        callbacks[reqId] = cb;
        routeMap[reqId] = route;
    };

    pomelo.notify = function(route, msg) {
        msg = msg || {};
        sendMessage(0, route, msg);
    };

    var sendMessage = function(reqId, route, msg) {
        var type = reqId ? Message.TYPE_REQUEST: Message.TYPE_NOTIFY;

        //compress message by Protobuf
        var protos = !!pomelo.data.protos ? pomelo.data.protos.client: {};
        if ( !! protos[route]) {
            msg = Protobuf.encode(route, msg);
        } else {
            msg = Protocol.strencode(JSON.stringify(msg));
        }

        var compressRoute = 0;
        if (pomelo.dict && pomelo.dict[route]) {
            route = pomelo.dict[route];
            compressRoute = 1;
        }

        msg = Message.encode(reqId, type, compressRoute, route, msg);
        var packet = Package.encode(Package.TYPE_DATA, msg);
        send(packet);
    };

    var send = function(packet) {
        socket.send(packet.buffer);
    };

    var heartbeat = function(data) {
        if (!heartbeatInterval) {
            // no heartbeat
            return;
        }

        var obj = Package.encode(Package.TYPE_HEARTBEAT);
        if (heartbeatTimeoutId) {
            clearTimeout(heartbeatTimeoutCb);
            heartbeatTimeoutId = null;
        }

        if (heartbeatId) {
            // already in a heartbeat interval
            return;
        }

        heartbeatId = (function() {
            heartbeatId = null;
            send(obj);
            nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
            heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout,0,0);
        },heartbeatInterval);

    };

    var setTimeout = function(fn,time){
        if(!!curScene){
            curScene.schedule(fn,time,0,0);
            return fn;
        }
        return null;
    };

    var clearTimeout = function(fn){
        if(!!curScene && !!fn){
            curScene.unschedule(fn);
        }
    };

    var heartbeatTimeoutCb = function() {
        var gap = nextHeartbeatTimeout - Date.now();
        if (gap > gapThreshold) {
            heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb,gap,0,0);
        } else {
            cc.error('server heartbeat timeout');
            pomelo.emit('heartbeat timeout');
            pomelo.disconnect();
        }
    };

    var handshake = function(data) {
        data = JSON.parse(Protocol.strdecode(data));
        if (data.code === RES_OLD_CLIENT) {
            pomelo.emit('error', 'client version not fullfill');
            return;
        }

        if (data.code !== RES_OK) {
            pomelo.emit('error', 'handshake fail');
            return;
        }

        handshakeInit(data);

        var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
        send(obj);
        if (initCallback) {
            initCallback(socket);
            initCallback = null;
        }
    };

    var onData = function(data) {
        //probuff decode
        var msg = Message.decode(data);

        if (msg.id > 0) {
            msg.route = routeMap[msg.id];
            delete routeMap[msg.id];
            if (!msg.route) {
                return;
            }
        }

        msg.body = deCompose(msg);

        processMessage(pomelo, msg);
    };

    var onKick = function(data) {
        pomelo.emit('onKick');
    };

    handlers[Package.TYPE_HANDSHAKE] = handshake;
    handlers[Package.TYPE_HEARTBEAT] = heartbeat;
    handlers[Package.TYPE_DATA] = onData;
    handlers[Package.TYPE_KICK] = onKick;

    var processPackage = function(msg) {
        handlers[msg.type](msg.body);
    };

    var processMessage = function(pomelo, msg) {
        if (!msg.id) {
            // server push message
            pomelo.emit(msg.route, msg.body);
        }

        //if have a id then find the callback function with the request
        var cb = callbacks[msg.id];

        delete callbacks[msg.id];
        if (typeof cb !== 'function') {
            return;
        }

        cb(msg.body);
        return;
    };

    var processMessageBatch = function(pomelo, msgs) {
        for (var i = 0,l = msgs.length; i < l; i++) {
            processMessage(pomelo, msgs[i]);
        }
    };

    var deCompose = function(msg) {
        var protos = !!pomelo.data.protos ? pomelo.data.protos.server: {};
        var abbrs = pomelo.data.abbrs;
        var route = msg.route;

        //Decompose route from dict
        if (msg.compressRoute) {
            if (!abbrs[route]) {
                return {};
            }

            route = msg.route = abbrs[route];
        }
        if ( !! protos[route]) {
            return Protobuf.decode(route, msg.body);
        } else {
            return JSON.parse(Protocol.strdecode(msg.body));
        }

        return msg;
    };

    var handshakeInit = function(data) {
        if (data.sys && data.sys.heartbeat) {
            heartbeatInterval = data.sys.heartbeat * 1000;  // heartbeat interval
            heartbeatTimeout = heartbeatInterval * 2;       // max heartbeat timeout
        } else {
            heartbeatInterval = 0;
            heartbeatTimeout = 0;
        }

        initData(data);

        if (typeof handshakeCallback === 'function') {
            handshakeCallback(data.user);
        }
    };

    //Initilize data used in pomelo client
    var initData = function(data) {
        if (!data || !data.sys) {
            return;
        }
        pomelo.data = pomelo.data || {};
        var dict = data.sys.dict;
        var protos = data.sys.protos;

        //Init compress dict
        if (dict) {
            pomelo.data.dict = dict;
            pomelo.data.abbrs = {};

            for (var route in dict) {
                pomelo.data.abbrs[dict[route]] = route;
            }
        }

        //Init Protobuf protos
        if (protos) {
            pomelo.data.protos = {
                server: protos.server || {},
                client: protos.client || {}
            };
            if (!!Protobuf) {
                Protobuf.init({
                    encoderProtos: protos.client,
                    decoderProtos: protos.server
                });
            }
        }
    };

})(HY.net);